package org.bjf.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.benmanes.caffeine.cache.Caffeine;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import lombok.extern.slf4j.Slf4j;
import org.bjf.cache.CacheNameConfig.LocalCacheEnum;
import org.bjf.cache.CacheNameConfig.RedisCacheEnum;
import org.bjf.cache.LiveCaffeineCache;
import org.bjf.cache.RedisCache;
import org.bjf.cache.SecondaryCacheManager;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.caffeine.CaffeineCache;
import org.springframework.cache.interceptor.CacheErrorHandler;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.cache.support.CompositeCacheManager;
import org.springframework.cache.support.SimpleCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;

@Configuration
@EnableCaching
@Slf4j
public class CacheConfig extends CachingConfigurerSupport {

  @Bean
  @Override
  public KeyGenerator keyGenerator() {
    return new KeyGenerator() {
      @Override
      public Object generate(Object target, Method method, Object... params) {
        StringBuilder sb = new StringBuilder();
        sb.append(target.getClass().getName());
        sb.append(":").append(method.getName());
        for (Object obj : params) {
          sb.append(":").append(obj.toString());
        }
        return sb.toString();
      }
    };
  }

  @Bean
  public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {

    // 定义value的序列化方式
    Jackson2JsonRedisSerializer jsonSerializer = new Jackson2JsonRedisSerializer(Object.class);
    ObjectMapper om = new ObjectMapper();
    om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
    om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
    jsonSerializer.setObjectMapper(om);

    RedisTemplate template = new RedisTemplate();
    template.setConnectionFactory(factory);
    template.setKeySerializer(template.getStringSerializer());
    template.setValueSerializer(jsonSerializer);
    template.setHashKeySerializer(template.getStringSerializer());
    template.setHashValueSerializer(jsonSerializer);
    template.afterPropertiesSet();

    return template;
  }

  /**
   * 本地缓存 Caffeine Cache
   */
  @Bean
  public CacheManager caffeineCacheManager() {
    ArrayList<CaffeineCache> caches = new ArrayList<>();
    for (LocalCacheEnum cache : LocalCacheEnum.values()) {
      LiveCaffeineCache liveCaffeineCache = new LiveCaffeineCache(cache.name(),
          Caffeine.newBuilder().recordStats()
              .expireAfterWrite(cache.getTimeout(), TimeUnit.SECONDS)
              .maximumSize(cache.getCapacity())
              .build());
      caches.add(liveCaffeineCache);
    }
    SimpleCacheManager cacheManager = new SimpleCacheManager();
    cacheManager.setCaches(caches);
    cacheManager.afterPropertiesSet();
    return cacheManager;
  }

  /**
   * redis cache
   */
  @Bean
  public CacheManager redisCacheManager(RedisTemplate redisTemplate) {

    ArrayList<RedisCache> caches = new ArrayList<>();
    //取自定义的缓存值
    for (RedisCacheEnum cache : RedisCacheEnum.values()) {
      caches.add(new RedisCache(redisTemplate, cache.name(), cache.getTimeout()));
    }
    SimpleCacheManager cacheManager = new SimpleCacheManager();
    cacheManager.setCaches(caches);

    return cacheManager;
  }

  /**
   * 自定义二级缓存 caffeine + redis
   */
  @Bean
  public CacheManager secondaryCacheManager(RedisTemplate redisTemplate) {
    return new SecondaryCacheManager(redisTemplate);
  }

  @Bean
  @Primary
  public CompositeCacheManager compositeCacheManager(CacheManager caffeineCacheManager,
      CacheManager redisCacheManager, CacheManager secondaryCacheManager) {
    CompositeCacheManager cacheManager = new CompositeCacheManager();
    cacheManager
        .setCacheManagers(
            Arrays.asList(caffeineCacheManager, redisCacheManager, secondaryCacheManager));
    cacheManager.setFallbackToNoOpCache(Boolean.TRUE);
    return cacheManager;
  }

  @Override
  public CacheErrorHandler errorHandler() {
    return new LiveCacheErrorHandler();
  }

  /**
   * 缓存异常统一处理,读写缓存不可用的时候,保证业务正常
   */
  private static class LiveCacheErrorHandler implements CacheErrorHandler {

    @Override
    public void handleCacheGetError(RuntimeException ex, Cache cache, Object key) {
      log.error("读取缓存失败，name=" + cache.getName() + ",key=" + key, ex);
    }

    @Override
    public void handleCachePutError(RuntimeException ex, Cache cache, Object key, Object value) {
      log.error("写入缓存失败，name=" + cache.getName() + ",key=" + key, ex);
    }

    @Override
    public void handleCacheEvictError(RuntimeException exception, Cache cache, Object key) {
      throw exception;
    }

    @Override
    public void handleCacheClearError(RuntimeException exception, Cache cache) {
      throw exception;
    }
  }
}
